import React, { createRef, PureComponent } from 'react'
import styles from './index.module.css'

export default class RecordVideoCall extends PureComponent {
  constructor(props){
    super(props)
    this.state = {

    }
    this.localVideoRef = createRef()
    this.remoteVideoRef = createRef()
  }

  componentDidMount(){
    let localVideo, remoteVideo
    if (this.localVideoRef.current) {
      localVideo = this.localVideoRef.current
    }
    if (this.remoteVideoRef.current) {
      remoteVideo = this.remoteVideoRef.current
    }

    const socket = new WebSocket('ws://localhost:3000')
    // 创建RTCPeerConnection对象
    const peer = new RTCPeerConnection()
    

    navigator.mediaDevices.getUserMedia({video: true, audio: true}).then(stream => {
      localVideo.srcObject = stream
      stream.getTracks().forEach(track => {
        peer.addTrack(track, stream)
      })
    })

    // 创建发起方会话描述对象
    peer.createOffer().then(offer => {
      peer.setLocalDescription(offer)
      socket.send(JSON.stringify(offer))
    })

    // 等对方接收到信息  通过调用 addIceCandidate 将接收的候选者信息传递给浏览器的ICE代理
    peer.onicecandidate = (e) => {
      if (e.candidate) {
        socket.send(JSON.stringify({
          type: 'offer_ice',
          iceCandidate: e.candidate
        }))
      }
    }

    socket.onmessage = (e) => {
      const {type, iceCandidate, sdp} = JSON.parse(e.data)
      if (type === 'offer_ice') {
        peer.addIceCandidate(iceCandidate)
      }
      if (type === 'offer') {
        navigator.mediaDevices.getUserMedia({video: true, audio: true}).then(stream => {
          remoteVideo.srcObject = stream
          stream.getTracks().forEach(track => {
            peer.addTrack(track, stream)
          })
        })
        const offerSdp = new RTCSessionDescription({type, sdp})
        peer.setRemoteDescription(offerSdp).then(() => {
          peer.createAnswer(answer => {
            socket.send(JSON.stringify(answer))
            peer.setLocalDescription(answer)
          })
        })
      }
    }

    peer.ontrack = e => {
      if (e && e.streams) {
        remoteVideo.srcObject = e.streams[0];
      }
    }

  }

  render() {
    return (
      <div className={styles.container}>
        <video ref={this.localVideoRef} 
          autoPlay 
          playsInline 
        ></video>
        <video ref={this.remoteVideoRef} 
          autoPlay 
          playsInline 
        ></video>
      </div>
    )
  }
}


/**
 * 说明
 * 1.相关技术
    本示例主要使用了 WebRTC 和 WebSocket：
    WebRTC（Web Real-Time Communication）即网页即时通信，是一个支持网页浏览器进行实时语音对话或视频对话的API。
    WebSocket是一种在单个TCP连接上进行全双工通信的协议。在 WebSocket 中，浏览器和服务器只需要完成一次握手，两者之间就直接可以创建持久性的连接，并进行双向数据传输。
  2.通话建立流程
    简单说一下流程，如浏览器 A 想和浏览器 B 进行音视频通话：
    A、B 都连接信令服务器（ws）；
    A 创建本地视频，并获取会话描述对象（offer sdp）信息；
    A 将 offer sdp 通过 ws 发送给 B；
    B 收到信令后，B 创建本地视频，并获取会话描述对象（answer sdp）信息；
    B 将 answer sdp 通过 ws 发送给 A；
    A 和 B 开始打洞，收集并通过 ws 交换 ice 信息；
    完成打洞后，A 和 B 开始为安全的媒体通信协商秘钥；
    至此， A 和 B 可以进行音视频通话。

  通信双方在建立连接前需要交换信息，这也就是开头提到的 WebSocket 充当的角色：信令服务器，用于转发信息。
  而 WebRTC 不借助中间媒介 的意思是，在建立对等连接后，不需要借助第三方服务器中转，而是直接在两个实体（浏览器）间进行传输。

  第一步
    获取视频标签，连接信令服务器，创建 RTCPeerConnection 对象。其中 RTCPeerConnection 的作用是在两个对等端之间建立连接，其构造函数支持传一个配置对象，包含ICE“打洞”（由于本示例在本机进行测试，故不需要）。

    const localVideo = document.querySelector('#local-video');
    const remoteVideo = document.querySelector('#remote-video');
    const socket = new WebSocket('ws://localhost:8080');
    const peer = new RTCPeerConnection();

    socket.onmessage = () => { // todo }
    peer.ontrack = () => { // todo }
    peer.onicecandidate = () => { // todo }
  第二步
    获取本地摄像头/麦克风（需要允许使用权限），拿到本地媒体流（MediaStream）后，需要将其中所有媒体轨道（MediaStreamTrack）添加到轨道集，这些轨道将被发送到另一对等方。

    navigator.mediaDevices.getUserMedia({ video: true, audio: true })
        .then(stream => {
            localVideo.srcObject = stream;
            stream.getTracks().forEach(track => {
                peer.addTrack(track, stream);
            });
        });
  第三步
    创建发起方会话描述对象（createOffer），设置本地SDP（setLocalDescription），并通过信令服务器发送到对等端，以启动与远程对等端的新WebRTC连接。

    peer.createOffer().then(offer => {
        peer.setLocalDescription(offer);
        socket.send(JSON.stringify(offer));
    });
    当调用 setLocalDescription 方法，PeerConnection 开始收集候选人（ice信息），并发送offer_ice到对等方。这边补充第一步中的peer.onicecandidate和socket.onmessage

    对等方收到ice信息后，通过调用 addIceCandidate 将接收的候选者信息传递给浏览器的ICE代理。

    peer.onicecandidate = e => {
        if (e.candidate) {
            socket.send(JSON.stringify({
                type: 'offer_ice',
                iceCandidate: e.candidate
            }));
        } 
    };

    socket.onmessage = e => {
        const { type, sdp, iceCandidate } = JSON.parse(e.data);
        if (type === 'offer_ice') {
            peer.addIceCandidate(iceCandidate);
        }
    }
  第四步
    接收方收到了offer信令后，开始获取摄像头/麦克风，与发起方操作一致。同时将收到offer SDP指定为连接的远程对等方属性（setRemoteDescription），并创建应答SDP（createAnswer），发送到对等端。这边补充第一步中的socket.onmessage。

    socket.onmessage = e => {
        const { type, sdp, iceCandidate } = JSON.parse(e.data);
        if (type === 'offer') {
            navigator.mediaDevices.getUserMedia();        // 与发起方一致，省略
            const offerSdp = new RTCSessionDescription({ type, sdp });
            peer.setRemoteDescription(offerSdp).then(() => {
                peer.createAnswer(answer => {
                    socket.send(JSON.stringify(answer));
                    peer.setLocalDescription(answer)
                });
            });
        }
    }
    注意：当 setLocalDescription 方法调用后，开始收集候选人信息，并发送 answer_ice 到对等方。与发送方同理，不赘述。

  第五步
    通过不断收集ICE信息（onicecandidate），发起方和应答方最终将建立一条最优的连接方式，此时会触发 ontrack 回调，即可获取到对等方的媒体流。

    peer.ontrack = e => {
        if (e && e.streams) {
            remoteVideo.srcObject = e.streams[0];
        }
    };
 */